/**************************************************************************** 
 * Meta，一个轻量级的静态分区Hypervisor 
 *
 * 版权(c) , 2022-
 *
 * 作者:
 *      Tupelo Shen <shenwanjiang2013@163.com>
 *
 * Meta是一个自由软件，你可以在遵循GNU GPLv2协议的条件下，重新修改并发布它。
 *
 ***************************************************************************/

#include <meta.h>
#include <page_table.h>
#include <arch/sysregs.h>
#include <cpu.h>

/****************************************************************************
 ****************************** 变量初始化 **********************************
 ****************************************************************************/

/**
 * stage-1页表描述
 * 
 * 修订历史：
 *      1.  PT_LVLS直接使用数字4。（2023-06-01 Tupelo Shen）
 */
struct page_table_dscr armv8_pt_dscr = {
    .lvls       = 4,
    .lvl_wdt    = (size_t[]){48, 39, 30, 21},
    .lvl_off    = (size_t[]){39, 30, 21, 12},
    .lvl_term   = (bool[]){false, true, true, true},
};

/**
 * This might be modified at initialization depending on the
 * value of parange and consequently SL0 in VTCR_EL2.
 * stage-2页表描述
 * 
 * 修订历史：
 *      1.  PT_LVLS直接使用数字4。（2023-06-01 Tupelo Shen）
 */
struct page_table_dscr armv8_pt_s2_dscr = {
    .lvls       = 4,
    .lvl_wdt    = (size_t[]){48, 39, 30, 21},
    .lvl_off    = (size_t[]){39, 30, 21, 12},
    .lvl_term   = (bool[]){false, true, true, true},
};

size_t parange_table[] = {32, 36, 40, 42, 44, 48};

struct page_table_dscr* hyp_pt_dscr  = &armv8_pt_dscr;
struct page_table_dscr* vm_pt_dscr   = &armv8_pt_s2_dscr;

size_t parange __attribute__((section(".data")));

/****************************************************************************
 ************************** 公共函数定义 ************************************
 ****************************************************************************/
/**
 * 功能描述: 将page table添加到虚拟核的root页表的index处
 * 入口参数: pt:    页表
 *           index: 页表在根页表中的索引
 * 出口参数: void
 * 返 回 值: void
 * 修改日期: 2022-09-21
 */
void pt_set_recursive(struct page_table* pt, size_t index)
{
    paddr_t pa;

    /* 将页表的root地址（虚拟），转换成物理地址 */
    mem_translate(&cpu.as, (vaddr_t)pt->root, &pa);
    /* 位于root页表中的索引值 */
    pte_t* pte = cpu.as.pt.root + index;
    /* 将物理地址转换成pte项，写入到第一级页表中 */
    pte_set(pte, pa, PTE_TABLE|PTE_HYP_FLAGS);
    pt->root_flags &= ~PT_ROOT_FLAGS_REC_IND_MSK;
    pt->root_flags |=
        (index << PT_ROOT_FLAGS_REC_IND_OFF) & PT_ROOT_FLAGS_REC_IND_MSK;
}

/**
 * 功能描述: 根据指定的虚拟地址和页表等级，获取在页表中的项
 * 入口参数: pt:        页表
 *           lvl:       页表级数
 *           va:        指定的虚拟地址
 * 出口参数: void
 * 返 回 值: addr:      页表项的地址
 * 修改日期: 2022-09-21
 */
pte_t* pt_get_pte(struct page_table* pt, size_t lvl, vaddr_t va)
{
    struct page_table* cpu_pt = &cpu.as.pt;

    size_t  rec_ind_off = cpu_pt->dscr->lvl_off[cpu_pt->dscr->lvls - lvl - 1];
    size_t  rec_ind_len = cpu_pt->dscr->lvl_wdt[cpu_pt->dscr->lvls - lvl - 1];
    pte_t   mask = (1UL << rec_ind_off) - 1;
    pte_t   rec_ind_mask = ((1UL << rec_ind_len) - 1) & ~mask;
    size_t  rec_ind = ((pt->root_flags & PT_ROOT_FLAGS_REC_IND_MSK) >>
                        PT_ROOT_FLAGS_REC_IND_OFF);
    pte_t   addr = ~mask;
    addr &= PTE_ADDR_MSK;
    addr &= ~(rec_ind_mask);
    addr |= ((rec_ind << rec_ind_off) & rec_ind_mask);
    addr |= (((va >> pt->dscr->lvl_off[lvl]) * sizeof(pte_t)) & (mask));

    return (pte_t*)addr;
}

/**
 * 功能描述: 根据虚拟地址和页表级数获取其所在的页表项的地址
 * 入口参数: pt:        页表
 *           lvl:       页表级数
 *           va:        指定的虚拟地址
 * 出口参数: void
 * 返 回 值: pte_t*:    页表项的地址
 * 修订历史：
 *      1.  将PAGE_SIZE宏修改为pt_size()函数（2023-06-01 Tupelo Shen）
 */
pte_t* pt_get(struct page_table* pt, size_t lvl, vaddr_t va)
{
    if (lvl == 0) return pt->root;

    uintptr_t pte = (uintptr_t)pt_get_pte(pt, lvl, va);
    pte &= ~(pt_size(pt, lvl) - 1);
    return (pte_t*)pte;
}

pte_t pt_pte_type(struct page_table* pt, size_t lvl)
{
    return (lvl == pt->dscr->lvls - 1) ? PTE_PAGE : PTE_SUPERPAGE;
}

bool pte_page(struct page_table* pt, pte_t* pte, size_t lvl)
{
    if (lvl != pt->dscr->lvls - 1) {
        return false;
    }

    return (*pte & PTE_TYPE_MSK) == PTE_PAGE;
}

bool pte_table(struct page_table* pt, pte_t* pte, size_t lvl)
{
    if (lvl == pt->dscr->lvls - 1) {
        return false;
    }

    return (*pte & PTE_TYPE_MSK) == PTE_TABLE;
}
